Micron Document
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| SparkN0de-git | SparkN0de |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Commit 2eef1cdf70f1dc62743cb60919ead111088289cb


Parents : 926b9c5
Author : Ivan <e46112d44649266d71fe2193e00a4710>
Signature : T66BB85Valid, signed by author
Date : 2026-07-17T22:15:10-05:00

feat: liberate some frontend with custom well-tested and future-proofed libs

Changes
Diff

diff --git a/electron/desktopPrivacySettings.js b/electron/desktopPrivacySettings.js
index 7be89c4a..82f3d425 100644
--- a/electron/desktopPrivacySettings.js
+++ b/electron/desktopPrivacySettings.js
@@ -20,9 +20,7 @@ function normalizeDesktopPrivacySettings(raw) {
}
return {
screenSecurityEnabled:
- typeof raw.screenSecurityEnabled === "boolean"
- ? raw.screenSecurityEnabled
- : defaults.screenSecurityEnabled,
+ typeof raw.screenSecurityEnabled === "boolean" ? raw.screenSecurityEnabled : defaults.screenSecurityEnabled,
};
}

diff --git a/meshchatx.rsm b/meshchatx.rsm
index 29960c37..238da675 100644
Binary files a/meshchatx.rsm and b/meshchatx.rsm differ

diff --git a/meshchatx/src/frontend/components/TutorialPrivacyStep.vue b/meshchatx/src/frontend/components/TutorialPrivacyStep.vue
index 660bb169..9a4830bf 100644
--- a/meshchatx/src/frontend/components/TutorialPrivacyStep.vue
+++ b/meshchatx/src/frontend/components/TutorialPrivacyStep.vue
@@ -49,9 +49,7 @@
/>
<span class="setting-toggle__label">
<span class="setting-toggle__title">{{ $t("settings.android_block_screenshots") }}</span>
- <span class="setting-toggle__description">{{
- $t("settings.android_block_screenshots_desc")
- }}</span>
+ <span class="setting-toggle__description">{{ $t("settings.android_block_screenshots_desc") }}</span>
</span>
</label>

diff --git a/meshchatx/src/frontend/components/messages/ConversationPeerHeader.vue b/meshchatx/src/frontend/components/messages/ConversationPeerHeader.vue
index 920732f1..71e0cfd6 100644
--- a/meshchatx/src/frontend/components/messages/ConversationPeerHeader.vue
+++ b/meshchatx/src/frontend/components/messages/ConversationPeerHeader.vue
@@ -173,8 +173,7 @@
<script>
import Utils from "../../js/Utils";
-import dayjs from "dayjs";
-import relativeTime from "dayjs/plugin/relativeTime";
+import { fromNow } from "../../libs/datetime.js";
import MaterialDesignIcon from "../MaterialDesignIcon.vue";
import IconButton from "../IconButton.vue";
import LxmfUserIcon from "../LxmfUserIcon.vue";
@@ -182,8 +181,6 @@ import ConversationDropDownMenu from "./ConversationDropDownMenu.vue";
import DropDownMenu from "../DropDownMenu.vue";
import DropDownMenuItem from "../DropDownMenuItem.vue";
-dayjs.extend(relativeTime);
-
export default {
name: "ConversationPeerHeader",
components: {
@@ -341,7 +338,7 @@ export default {
if (ms == null) {
return "";
}
- return dayjs(ms).fromNow();
+ return fromNow(ms);
},
},
methods: {

diff --git a/meshchatx/src/frontend/components/messages/ConversationViewer.vue b/meshchatx/src/frontend/components/messages/ConversationViewer.vue
index 324e0418..6a50754d 100644
--- a/meshchatx/src/frontend/components/messages/ConversationViewer.vue
+++ b/meshchatx/src/frontend/components/messages/ConversationViewer.vue
@@ -1813,10 +1813,7 @@ import {
import MicrophoneRecorder from "../../js/MicrophoneRecorder";
import WebSocketConnection from "../../js/WebSocketConnection";
import AddAudioButton from "./AddAudioButton.vue";
-import dayjs from "dayjs";
-import relativeTime from "dayjs/plugin/relativeTime";
-
-dayjs.extend(relativeTime);
+import { fromNow } from "../../libs/datetime.js";
const SCROLL_SETTLE_MAX_PASSES = 24;
const OPEN_CONVERSATION_SCROLL_PIN_MS = 900;
@@ -1845,7 +1842,7 @@ import "emoji-picker-element";
import StickerView from "../stickers/StickerView.vue";
import InViewAnimatedImg from "./InViewAnimatedImg.vue";
import TelemetryHistoryModal from "./telemetry/TelemetryHistoryModal.vue";
-import { v4 as uuidv4 } from "uuid";
+import { uuidv4 } from "../../libs/uuid.js";
export default {
name: "ConversationViewer",
@@ -3845,7 +3842,7 @@ export default {
// check if we have an outbound ticket available
if (outboundTicketExpiry != null) {
- estimatedTimeForStamp = `instant (ticket expires ${dayjs(outboundTicketExpiry * 1000).fromNow()})`;
+ estimatedTimeForStamp = `instant (ticket expires ${fromNow(outboundTicketExpiry * 1000)})`;
}
DialogUtils.alert(

diff --git a/meshchatx/src/frontend/components/messages/telemetry/TelemetryBatteryChart.vue b/meshchatx/src/frontend/components/messages/telemetry/TelemetryBatteryChart.vue
index 98df0942..78392e9d 100644
--- a/meshchatx/src/frontend/components/messages/telemetry/TelemetryBatteryChart.vue
+++ b/meshchatx/src/frontend/components/messages/telemetry/TelemetryBatteryChart.vue
@@ -144,7 +144,7 @@ import {
clampBatteryPercent,
interpolateBatteryByTime,
} from "../../../js/telemetryBatteryChartSpec.js";
-import dayjs from "dayjs";
+import { formatDate } from "../../../libs/datetime.js";
export default {
name: "TelemetryBatteryChart",
@@ -178,11 +178,11 @@ export default {
},
startLabel() {
if (!this.samples.length) return "";
- return dayjs(this.samples[0].x * 1000).format("MMM D, HH:mm");
+ return formatDate(this.samples[0].x * 1000, "MMM D, HH:mm");
},
endLabel() {
if (!this.samples.length) return "";
- return dayjs(this.samples[this.samples.length - 1].x * 1000).format("MMM D, HH:mm");
+ return formatDate(this.samples[this.samples.length - 1].x * 1000, "MMM D, HH:mm");
},
hoverTipStyle() {
if (!this.hover) return {};
@@ -226,7 +226,7 @@ export default {
gx,
gy,
pct: Math.round(y),
- timeLabel: dayjs(ts * 1000).format("MMM D, HH:mm"),
+ timeLabel: formatDate(ts * 1000, "MMM D, HH:mm"),
tipLeft,
tipTop,
};

diff --git a/meshchatx/src/frontend/components/settings/SettingsPage.vue b/meshchatx/src/frontend/components/settings/SettingsPage.vue
index ae93ba39..1b12517a 100644
--- a/meshchatx/src/frontend/components/settings/SettingsPage.vue
+++ b/meshchatx/src/frontend/components/settings/SettingsPage.vue
@@ -2819,7 +2819,9 @@
v-if="showWindowsScreenSecurity"
class="p-4 rounded-2xl border border-amber-200 dark:border-amber-900/40 bg-amber-50/80 dark:bg-amber-950/30 space-y-3"
>
- <div class="text-xs font-semibold uppercase tracking-wider text-amber-800 dark:text-amber-200">
+ <div
+ class="text-xs font-semibold uppercase tracking-wider text-amber-800 dark:text-amber-200"
+ >
{{ $t("app.screen_security_drm_eyebrow") }}
</div>
<label class="setting-toggle">

diff --git a/meshchatx/src/frontend/js/GlobalEmitter.js b/meshchatx/src/frontend/js/GlobalEmitter.js
index 2a3d4fd2..74d02b26 100644
--- a/meshchatx/src/frontend/js/GlobalEmitter.js
+++ b/meshchatx/src/frontend/js/GlobalEmitter.js
@@ -1,8 +1,10 @@
-import mitt from "mitt";
+// SPDX-License-Identifier: 0BSD
+
+import { createEmitter } from "../libs/emitter.js";
class GlobalEmitter {
constructor() {
- this.emitter = mitt();
+ this.emitter = createEmitter();
}
// add event listener

diff --git a/meshchatx/src/frontend/js/Utils.js b/meshchatx/src/frontend/js/Utils.js
index 5ef750db..3031a02a 100644
--- a/meshchatx/src/frontend/js/Utils.js
+++ b/meshchatx/src/frontend/js/Utils.js
@@ -1,4 +1,4 @@
-import dayjs from "dayjs";
+import { formatDate } from "../libs/datetime.js";
class Utils {
static formatDestinationHash(destinationHashHex) {
@@ -98,7 +98,7 @@ class Utils {
// If older than 24 hours, show full date
if (diffSec > 86400) {
- return dayjs(date).format("MMM D, h:mm A");
+ return formatDate(date, "MMM D, h:mm A");
}
return this.formatSeconds(diffSec);
@@ -127,7 +127,7 @@ class Utils {
}
if (diffSec > 86400) {
- return dayjs(date).format("MMM D, h:mm A");
+ return formatDate(date, "MMM D, h:mm A");
}
return this.formatSecondsWithoutAgo(diffSec);
@@ -154,7 +154,7 @@ class Utils {
}
static convertUnixMillisToLocalDateTimeString(unixTimestampInMilliseconds) {
- return dayjs(unixTimestampInMilliseconds).format("YYYY-MM-DD hh:mm A");
+ return formatDate(unixTimestampInMilliseconds, "YYYY-MM-DD hh:mm A");
}
static convertDateTimeToLocalDateTimeString(dateTime) {

diff --git a/meshchatx/src/frontend/js/WebSocketConnection.js b/meshchatx/src/frontend/js/WebSocketConnection.js
index c5d9567c..f942608f 100644
--- a/meshchatx/src/frontend/js/WebSocketConnection.js
+++ b/meshchatx/src/frontend/js/WebSocketConnection.js
@@ -1,4 +1,4 @@
-import mitt from "mitt";
+import { createEmitter } from "../libs/emitter.js";
import { reconnectDelayWithJitterMs } from "./wsConnectionSupport";
const PING_INTERVAL_MS = 25000;
@@ -9,7 +9,7 @@ const JITTER_MAX_MS = 400;
class WebSocketConnection {
constructor() {
- this.emitter = mitt();
+ this.emitter = createEmitter();
this.ws = null;
this._heartbeatInterval = null;
this._pongTimeout = null;

diff --git a/meshchatx/src/frontend/js/registries/wsEventRegistry.js b/meshchatx/src/frontend/js/registries/wsEventRegistry.js
index bef69d53..d06cf575 100644
--- a/meshchatx/src/frontend/js/registries/wsEventRegistry.js
+++ b/meshchatx/src/frontend/js/registries/wsEventRegistry.js
@@ -1,9 +1,9 @@
// SPDX-License-Identifier: 0BSD
-import mitt from "mitt";
+import { createEmitter } from "../../libs/emitter.js";
-/** @type {import('mitt').Emitter<Record<string, unknown>>} */
-const emitter = mitt();
+/** @type {ReturnType<typeof createEmitter>} */
+const emitter = createEmitter();
/**
* @param {string} type
@@ -27,7 +27,7 @@ export function offWsEvent(type, handler) {
*/
export async function dispatchWsEvent(type, payload) {
const handlers = emitter.all.get(type);
- if (!handlers || handlers.size === 0) {
+ if (!handlers || handlers.length === 0) {
return;
}
for (const handler of handlers) {

diff --git a/meshchatx/src/frontend/libs/clickOutside.js b/meshchatx/src/frontend/libs/clickOutside.js
new file mode 100644
index 00000000..aa227072
--- /dev/null
+++ b/meshchatx/src/frontend/libs/clickOutside.js
@@ -0,0 +1,195 @@
+// SPDX-License-Identifier: 0BSD
+
+/**
+ * Vue 3 click-outside directive and plugin.
+ * Compatible with the previous click-outside-vue3 binding shapes:
+ * v-click-outside="handler"
+ * v-click-outside="{ handler, middleware, events, isActive, detectIframe, capture }"
+ */
+
+const HANDLERS_PROPERTY = "__meshchatx_click_outside";
+
+const HAS_WINDOW = typeof window !== "undefined";
+const HAS_NAVIGATOR = typeof navigator !== "undefined";
+
+const IS_TOUCH =
+ HAS_WINDOW && ("ontouchstart" in window || (HAS_NAVIGATOR && Number(navigator.maxTouchPoints || 0) > 0));
+
+const DEFAULT_EVENTS = IS_TOUCH ? ["touchstart"] : ["click"];
+
+/**
+ * @param {unknown} bindingValue
+ */
+export function processDirectiveArguments(bindingValue) {
+ const isFunction = typeof bindingValue === "function";
+ if (!isFunction && (bindingValue == null || typeof bindingValue !== "object")) {
+ throw new Error("v-click-outside: Binding value must be a function or an object");
+ }
+ const value = /** @type {Record<string, unknown>} */ (isFunction ? {} : bindingValue);
+ const handler = isFunction ? bindingValue : value.handler;
+ if (typeof handler !== "function") {
+ throw new Error("v-click-outside: handler must be a function");
+ }
+ return {
+ handler,
+ middleware: typeof value.middleware === "function" ? value.middleware : (item) => item,
+ events: Array.isArray(value.events) && value.events.length > 0 ? value.events : DEFAULT_EVENTS,
+ isActive: value.isActive !== false,
+ detectIframe: value.detectIframe !== false,
+ capture: Boolean(value.capture),
+ };
+}
+
+/**
+ * Compare bindings without JSON.stringify so function identity is preserved.
+ *
+ * @param {unknown} a
+ * @param {unknown} b
+ * @returns {boolean}
+ */
+export function bindingsEqual(a, b) {
+ if (a === b) {
+ return true;
+ }
+ if (typeof a === "function" || typeof b === "function") {
+ return a === b;
+ }
+ if (!a || !b || typeof a !== "object" || typeof b !== "object") {
+ return false;
+ }
+ const left = /** @type {Record<string, unknown>} */ (a);
+ const right = /** @type {Record<string, unknown>} */ (b);
+ return (
+ left.handler === right.handler &&
+ left.middleware === right.middleware &&
+ left.isActive === right.isActive &&
+ left.detectIframe === right.detectIframe &&
+ Boolean(left.capture) === Boolean(right.capture) &&
+ JSON.stringify(left.events ?? null) === JSON.stringify(right.events ?? null)
+ );
+}
+
+/**
+ * @param {{ event: Event, handler: Function, middleware: Function }} args
+ */
+export function execHandler({ event, handler, middleware }) {
+ if (middleware(event)) {
+ handler(event);
+ }
+}
+
+/**
+ * @param {{ el: Element, event: Event }} args
+ * @returns {boolean}
+ */
+export function isClickOutsideElement({ el, event }) {
+ const path =
+ typeof event.composedPath === "function" ? event.composedPath() : Array.isArray(event.path) ? event.path : null;
+ if (path) {
+ return path.indexOf(el) < 0;
+ }
+ const target = event.target;
+ if (!(target instanceof Node)) {
+ return true;
+ }
+ return !el.contains(target);
+}
+
+/**
+ * @param {{ el: Element, event: Event, handler: Function, middleware: Function }} args
+ */
+export function onFauxIframeClick({ el, event, handler, middleware }) {
+ setTimeout(() => {
+ const { activeElement } = document;
+ if (activeElement && activeElement.tagName === "IFRAME" && !el.contains(activeElement)) {
+ execHandler({ event, handler, middleware });
+ }
+ }, 0);
+}
+
+/**
+ * @param {{ el: Element, event: Event, handler: Function, middleware: Function }} args
+ */
+export function onOutsideEvent({ el, event, handler, middleware }) {
+ if (!isClickOutsideElement({ el, event })) {
+ return;
+ }
+ execHandler({ event, handler, middleware });
+}
+
+/**
+ * @param {Element} el
+ * @param {{ value: unknown }} binding
+ */
+export function beforeMount(el, { value }) {
+ const { events, handler, middleware, isActive, detectIframe, capture } = processDirectiveArguments(value);
+ if (!isActive) {
+ return;
+ }
+
+ el[HANDLERS_PROPERTY] = events.map((eventName) => ({
+ event: eventName,
+ srcTarget: document.documentElement,
+ handler: (event) => onOutsideEvent({ el, event, handler, middleware }),
+ capture,
+ }));
+
+ if (detectIframe) {
+ el[HANDLERS_PROPERTY].push({
+ event: "blur",
+ srcTarget: window,
+ handler: (event) => onFauxIframeClick({ el, event, handler, middleware }),
+ capture,
+ });
+ }
+
+ for (const entry of el[HANDLERS_PROPERTY]) {
+ setTimeout(() => {
+ if (!el[HANDLERS_PROPERTY]) {
+ return;
+ }
+ entry.srcTarget.addEventListener(entry.event, entry.handler, entry.capture);
+ }, 0);
+ }
+}
+
+/**
+ * @param {Element} el
+ */
+export function unmounted(el) {
+ const handlers = el[HANDLERS_PROPERTY] || [];
+ for (const entry of handlers) {
+ entry.srcTarget.removeEventListener(entry.event, entry.handler, entry.capture);
+ }
+ delete el[HANDLERS_PROPERTY];
+}
+
+/**
+ * @param {Element} el
+ * @param {{ value: unknown, oldValue: unknown }} binding
+ */
+export function updated(el, { value, oldValue }) {
+ if (bindingsEqual(value, oldValue)) {
+ return;
+ }
+ unmounted(el);
+ beforeMount(el, { value });
+}
+
+export const clickOutsideDirective = HAS_WINDOW
+ ? {
+ beforeMount,
+ updated,
+ unmounted,
+ }
+ : {};
+
+const plugin = {
+ install(app) {
+ app.directive("click-outside", clickOutsideDirective);
+ },
+ directive: clickOutsideDirective,
+};
+
+export { HANDLERS_PROPERTY, DEFAULT_EVENTS };
+export default plugin;

diff --git a/meshchatx/src/frontend/libs/datetime.js b/meshchatx/src/frontend/libs/datetime.js
new file mode 100644
index 00000000..753e65d9
--- /dev/null
+++ b/meshchatx/src/frontend/libs/datetime.js
@@ -0,0 +1,272 @@
+// SPDX-License-Identifier: 0BSD
+
+/**
+ * Local datetime helpers for MeshChatX.
+ * formatDate supports a frozen token subset. fromNow uses a frozen English golden table.
+ */
+
+export const MONTHS_SHORT = Object.freeze([
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec",
+]);
+
+/** Tokens accepted by formatDate. Unknown tokens stay literal. */
+export const SUPPORTED_FORMAT_TOKENS = Object.freeze([
+ "YYYY",
+ "MMM",
+ "MM",
+ "M",
+ "DD",
+ "D",
+ "HH",
+ "H",
+ "hh",
+ "h",
+ "mm",
+ "A",
+ "a",
+]);
+
+const TOKEN_RE = /YYYY|MMM|MM|M|DD|D|HH|H|hh|h|mm|A|a/g;
+
+/**
+ * Frozen relative-time thresholds. Do not change without updating golden tests.
+ * limitSec is inclusive upper bound for the absolute second delta.
+ */
+export const FROM_NOW_THRESHOLDS = Object.freeze([
+ Object.freeze({ limitSec: 44, past: "a few seconds ago", future: "in a few seconds" }),
+ Object.freeze({ limitSec: 89, past: "a minute ago", future: "in a minute" }),
+ Object.freeze({ limitSec: 44 * 60, unitSec: 60, unit: "minute" }),
+ Object.freeze({ limitSec: 89 * 60, past: "an hour ago", future: "in an hour" }),
+ Object.freeze({ limitSec: 21 * 3600, unitSec: 3600, unit: "hour" }),
+ Object.freeze({ limitSec: 35 * 3600, past: "a day ago", future: "in a day" }),
+ Object.freeze({ limitSec: 25 * 86400, unitSec: 86400, unit: "day" }),
+ Object.freeze({ limitSec: 45 * 86400, past: "a month ago", future: "in a month" }),
+ Object.freeze({ limitSec: 10 * 30 * 86400, unitSec: 30 * 86400, unit: "month" }),
+ Object.freeze({ limitSec: 17 * 365 * 86400, past: "a year ago", future: "in a year" }),
+ Object.freeze({ limitSec: Number.POSITIVE_INFINITY, unitSec: 365 * 86400, unit: "year" }),
+]);
+
+/**
+ * Golden samples for fromNow. Kept next to the implementation so drift is obvious.
+ * Each entry: [deltaSec, expectedPast, expectedFuture]
+ */
+export const FROM_NOW_GOLDEN = Object.freeze([
+ Object.freeze([0, "a few seconds ago", "a few seconds ago"]),
+ Object.freeze([1, "a few seconds ago", "in a few seconds"]),
+ Object.freeze([44, "a few seconds ago", "in a few seconds"]),
+ Object.freeze([45, "a minute ago", "in a minute"]),
+ Object.freeze([89, "a minute ago", "in a minute"]),
+ Object.freeze([90, "2 minutes ago", "in 2 minutes"]),
+ Object.freeze([120, "2 minutes ago", "in 2 minutes"]),
+ Object.freeze([44 * 60, "44 minutes ago", "in 44 minutes"]),
+ Object.freeze([45 * 60, "an hour ago", "in an hour"]),
+ Object.freeze([89 * 60, "an hour ago", "in an hour"]),
+ Object.freeze([90 * 60, "2 hours ago", "in 2 hours"]),
+ Object.freeze([21 * 3600, "21 hours ago", "in 21 hours"]),
+ Object.freeze([22 * 3600, "a day ago", "in a day"]),
+ Object.freeze([35 * 3600, "a day ago", "in a day"]),
+ Object.freeze([36 * 3600, "2 days ago", "in 2 days"]),
+ Object.freeze([2 * 86400, "2 days ago", "in 2 days"]),
+ Object.freeze([25 * 86400, "25 days ago", "in 25 days"]),
+ Object.freeze([26 * 86400, "a month ago", "in a month"]),
+ Object.freeze([45 * 86400, "a month ago", "in a month"]),
+ Object.freeze([46 * 86400, "2 months ago", "in 2 months"]),
+ Object.freeze([10 * 30 * 86400, "10 months ago", "in 10 months"]),
+ Object.freeze([11 * 30 * 86400, "a year ago", "in a year"]),
+ Object.freeze([17 * 365 * 86400, "a year ago", "in a year"]),
+ Object.freeze([18 * 365 * 86400, "18 years ago", "in 18 years"]),
+]);
+
+/**
+ * @param {unknown} input
+ * @returns {Date | null}
+ */
+export function toDate(input) {
+ if (input == null || input === "") {
+ return null;
+ }
+ if (input instanceof Date) {
+ const t = input.getTime();
+ return Number.isFinite(t) ? input : null;
+ }
+ if (typeof input === "number") {
+ if (!Number.isFinite(input)) {
+ return null;
+ }
+ const d = new Date(input);
+ return Number.isFinite(d.getTime()) ? d : null;
+ }
+ if (typeof input === "string") {
+ const d = new Date(input);
+ return Number.isFinite(d.getTime()) ? d : null;
+ }
+ return null;
+}
+
+/**
+ * @param {Date} date
+ * @param {string} token
+ * @returns {string}
+ */
+function formatToken(date, token) {
+ const month = date.getMonth();
+ const day = date.getDate();
+ const hours24 = date.getHours();
+ const minutes = date.getMinutes();
+ const year = date.getFullYear();
+ const hours12 = hours24 % 12 || 12;
+ const ampm = hours24 < 12 ? "AM" : "PM";
+
+ switch (token) {
+ case "YYYY":
+ return String(year).padStart(4, "0");
+ case "MMM":
+ return MONTHS_SHORT[month];
+ case "MM":
+ return String(month + 1).padStart(2, "0");
+ case "M":
+ return String(month + 1);
+ case "DD":
+ return String(day).padStart(2, "0");
+ case "D":
+ return String(day);
+ case "HH":
+ return String(hours24).padStart(2, "0");
+ case "H":
+ return String(hours24);
+ case "hh":
+ return String(hours12).padStart(2, "0");
+ case "h":
+ return String(hours12);
+ case "mm":
+ return String(minutes).padStart(2, "0");
+ case "A":
+ return ampm;
+ case "a":
+ return ampm.toLowerCase();
+ default:
+ return token;
+ }
+}
+
+/**
+ * True when every format token in pattern is in SUPPORTED_FORMAT_TOKENS.
+ * Literals and punctuation are ignored.
+ *
+ * @param {unknown} pattern
+ * @returns {boolean}
+ */
+export function isSupportedFormatPattern(pattern) {
+ if (typeof pattern !== "string" || pattern.length === 0) {
+ return false;
+ }
+ const matches = pattern.match(TOKEN_RE);
+ if (!matches) {
+ return true;
+ }
+ return matches.every((token) => SUPPORTED_FORMAT_TOKENS.includes(token));
+}
+
+/**
+ * Format a date with the frozen token subset.
+ * Supported: YYYY MMM MM M DD D HH H hh h mm A a
+ *
+ * @param {unknown} input
+ * @param {string} pattern
+ * @returns {string}
+ */
+export function formatDate(input, pattern) {
+ const date = toDate(input);
+ if (!date || typeof pattern !== "string" || pattern.length === 0) {
+ return "";
+ }
+ return pattern.replace(TOKEN_RE, (token) => formatToken(date, token));
+}
+
+/**
+ * @param {string} unit
+ * @param {number} n
+ * @param {boolean} isFuture
+ * @returns {string}
+ */
+function pluralUnitLabel(unit, n, isFuture) {
+ const word = n === 1 ? unit : `${unit}s`;
+ return isFuture ? `in ${n} ${word}` : `${n} ${word} ago`;
+}
+
+/**
+ * Pure relative label from absolute seconds and polarity.
+ *
+ * @param {number} absSec
+ * @param {boolean} isFuture
+ * @returns {string}
+ */
+export function relativeLabel(absSec, isFuture) {
+ const sec = Math.max(0, Math.round(Number(absSec) || 0));
+ for (const row of FROM_NOW_THRESHOLDS) {
+ if (sec > row.limitSec) {
+ continue;
+ }
+ if (row.unit) {
+ const n = Math.max(1, Math.round(sec / row.unitSec));
+ return pluralUnitLabel(row.unit, n, isFuture);
+ }
+ if (sec === 0) {
+ return row.past;
+ }
+ return isFuture ? row.future : row.past;
+ }
+ // FROM_NOW_THRESHOLDS always ends at Infinity. Kept as a safety net.
+ return isFuture ? "in a few seconds" : "a few seconds ago";
+}
+
+/**
+ * Relative time string from `input` toward `now` (default Date.now()).
+ *
+ * @param {unknown} input
+ * @param {unknown} [nowInput]
+ * @returns {string}
+ */
+export function fromNow(input, nowInput = Date.now()) {
+ const date = toDate(input);
+ const now = toDate(nowInput);
+ if (!date || !now) {
+ return "";
+ }
+ const diffMs = date.getTime() - now.getTime();
+ const absSec = Math.round(Math.abs(diffMs) / 1000);
+ const isFuture = diffMs > 0;
+ return relativeLabel(absSec, isFuture);
+}
+
+/**
+ * dayjs-like helper for call sites that previously did dayjs(x).format / .fromNow.
+ *
+ * @param {unknown} input
+ */
+export function meshDate(input) {
+ return {
+ format(pattern) {
+ return formatDate(input, pattern);
+ },
+ fromNow(nowInput) {
+ return fromNow(input, nowInput);
+ },
+ toDate() {
+ return toDate(input);
+ },
+ };
+}
+
+export default meshDate;

diff --git a/meshchatx/src/frontend/libs/emitter.js b/meshchatx/src/frontend/libs/emitter.js
new file mode 100644
index 00000000..fbd3fc34
--- /dev/null
+++ b/meshchatx/src/frontend/libs/emitter.js
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: 0BSD
+
+/**
+ * Small mitt-compatible event emitter.
+ * Handlers are stored as arrays on a Map keyed by event type.
+ * Wildcard listeners use type "*".
+ * Emit uses a snapshot so handlers may safely on/off during emit.
+ */
+
+/**
+ * @typedef {(event: unknown) => void} EmitterHandler
+ * @typedef {(type: string | symbol, event: unknown) => void} WildcardHandler
+ * @typedef {Map<string | symbol, Array<EmitterHandler | WildcardHandler>>} HandlerMap
+ *
+ * @typedef {object} Emitter
+ * @property {HandlerMap} all
+ * @property {(type: string | symbol, handler: EmitterHandler | WildcardHandler) => void} on
+ * @property {(type: string | symbol, handler?: EmitterHandler | WildcardHandler) => void} off
+ * @property {(type: string | symbol, event?: unknown) => void} emit
+ */
+
+/**
+ * @param {HandlerMap} [all]
+ * @returns {Emitter}
+ */
+export function createEmitter(all = new Map()) {
+ return {
+ all,
+ on(type, handler) {
+ if (typeof handler !== "function") {
+ return;
+ }
+ const list = all.get(type);
+ if (list) {
+ list.push(handler);
+ } else {
+ all.set(type, [handler]);
+ }
+ },
+ off(type, handler) {
+ const list = all.get(type);
+ if (!list) {
+ return;
+ }
+ if (handler == null) {
+ all.set(type, []);
+ return;
+ }
+ const index = list.indexOf(handler);
+ if (index >= 0) {
+ list.splice(index, 1);
+ }
+ },
+ emit(type, event) {
+ const list = all.get(type);
+ if (list) {
+ for (const handler of list.slice()) {
+ handler(event);
+ }
+ }
+ const wild = all.get("*");
+ if (wild) {
+ for (const handler of wild.slice()) {
+ handler(type, event);
+ }
+ }
+ },
+ };
+}
+
+/**
+ * @param {HandlerMap} [all]
+ * @returns {Emitter}
+ */
+export default function emitter(all) {
+ return createEmitter(all);
+}

diff --git a/meshchatx/src/frontend/libs/index.js b/meshchatx/src/frontend/libs/index.js
new file mode 100644
index 00000000..61180bae
--- /dev/null
+++ b/meshchatx/src/frontend/libs/index.js
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: 0BSD
+
+export { createEmitter, default as emitter } from "./emitter.js";
+export { randomUuidV4, uuidv4, isUuidV4, fillRandomBytes, resolveCrypto, UUID_V4_RE } from "./uuid.js";
+export {
+ formatDate,
+ fromNow,
+ relativeLabel,
+ meshDate,
+ toDate,
+ isSupportedFormatPattern,
+ FROM_NOW_GOLDEN,
+ FROM_NOW_THRESHOLDS,
+ SUPPORTED_FORMAT_TOKENS,
+ MONTHS_SHORT,
+ default as datetime,
+} from "./datetime.js";
+export {
+ default as clickOutside,
+ clickOutsideDirective,
+ processDirectiveArguments,
+ bindingsEqual,
+ isClickOutsideElement,
+ execHandler,
+ onOutsideEvent,
+ onFauxIframeClick,
+ beforeMount,
+ updated,
+ unmounted,
+ HANDLERS_PROPERTY,
+ DEFAULT_EVENTS,
+} from "./clickOutside.js";

diff --git a/meshchatx/src/frontend/libs/uuid.js b/meshchatx/src/frontend/libs/uuid.js
new file mode 100644
index 00000000..78b72fb6
--- /dev/null
+++ b/meshchatx/src/frontend/libs/uuid.js
@@ -0,0 +1,94 @@
+// SPDX-License-Identifier: 0BSD
+
+const UUID_V4_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
+
+/**
+ * @typedef {object} UuidCrypto
+ * @property {(array: Uint8Array) => Uint8Array} [getRandomValues]
+ * @property {() => string} [randomUUID]
+ */
+
+/**
+ * @returns {UuidCrypto | undefined}
+ */
+function getCrypto() {
+ if (typeof globalThis !== "undefined" && globalThis.crypto) {
+ return globalThis.crypto;
+ }
+ return undefined;
+}
+
+/**
+ * Resolve crypto source. Pass `crypto: null` to force the non-crypto fallback.
+ *
+ * @param {{ crypto?: UuidCrypto | null }} [options]
+ * @returns {UuidCrypto | null | undefined}
+ */
+export function resolveCrypto(options = {}) {
+ if (Object.prototype.hasOwnProperty.call(options, "crypto")) {
+ return options.crypto;
+ }
+ return getCrypto();
+}
+
+/**
+ * Fill a Uint8Array with cryptographically strong random values when possible.
+ * Falls back to Math.random only when Web Crypto is unavailable.
+ * Do not use the Math.random path for secrets.
+ *
+ * @param {Uint8Array} bytes
+ * @param {{ crypto?: UuidCrypto | null }} [options]
+ * @returns {Uint8Array}
+ */
+export function fillRandomBytes(bytes, options = {}) {
+ if (!(bytes instanceof Uint8Array)) {
+ throw new TypeError("fillRandomBytes expects a Uint8Array");
+ }
+ const c = resolveCrypto(options);
+ if (c && typeof c.getRandomValues === "function") {
+ c.getRandomValues(bytes);
+ return bytes;
+ }
+ for (let i = 0; i < bytes.length; i++) {
+ bytes[i] = Math.floor(Math.random() * 256) & 0xff;
+ }
+ return bytes;
+}
+
+/**
+ * RFC 4122 version 4 UUID string.
+ *
+ * @param {{ crypto?: UuidCrypto | null }} [options]
+ * @returns {string}
+ */
+export function randomUuidV4(options = {}) {
+ const c = resolveCrypto(options);
+ if (c && typeof c.randomUUID === "function") {
+ return c.randomUUID();
+ }
+ const bytes = fillRandomBytes(new Uint8Array(16), { crypto: c ?? null });
+ bytes[6] = (bytes[6] & 0x0f) | 0x40;
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
+ const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
+}
+
+/**
+ * Alias matching the previous `uuid` package import style.
+ *
+ * @param {{ crypto?: UuidCrypto | null }} [options]
+ * @returns {string}
+ */
+export function uuidv4(options) {
+ return randomUuidV4(options);
+}
+
+/**
+ * @param {unknown} value
+ * @returns {boolean}
+ */
+export function isUuidV4(value) {
+ return typeof value === "string" && UUID_V4_RE.test(value);
+}
+
+export { UUID_V4_RE };

diff --git a/meshchatx/src/frontend/main.js b/meshchatx/src/frontend/main.js
index cbb652ab..a375ca40 100644
--- a/meshchatx/src/frontend/main.js
+++ b/meshchatx/src/frontend/main.js
@@ -1,7 +1,7 @@
import { createApp } from "vue";
import { createRouter, createWebHashHistory } from "vue-router";
import { createI18n } from "vue-i18n";
-import vClickOutside from "click-outside-vue3";
+import vClickOutside from "./libs/clickOutside.js";
import DOMPurify from "dompurify";
import "./style.css";
import { injectMeshchatThemeVariables, vuetifyThemesFromTokens } from "./theme/designTokens.js";

diff --git a/package.json b/package.json
index 3fca3855..51d122e7 100644
--- a/package.json
+++ b/package.json
@@ -77,6 +77,7 @@
"@vitest/ui": "^4.1.9",
"@vue/test-utils": "^2.4.11",
"cross-env": "^10.1.0",
+ "dayjs": "^1.11.21",
"electron": "42.4.0",
"electron-builder": "^26.15.3",
"electron-builder-squirrel-windows": "^26.15.3",
@@ -289,9 +290,7 @@
"@mdi/js": "^7.4.47",
"@tailwindcss/forms": "^0.5.11",
"@tanstack/vue-virtual": "^3.13.30",
- "click-outside-vue3": "^4.0.1",
"compressorjs": "^1.3.0",
- "dayjs": "^1.11.21",
"dompurify": ">=3.4.11",
"electron-prompt": "^1.7.0",
"emoji-picker-element": "^1.29.1",
@@ -299,11 +298,9 @@
"jsqr": "^1.4.0",
"jszip": "^3.10.1",
"marked": "^18.0.5",
- "mitt": "^3.0.1",
"ol": "^10.9.0",
"ol-mapbox-style": "^13.4.1",
"qrcode": "^1.5.4",
- "uuid": "^14.0.1",
"vis-data": "^7.1.10",
"vis-network": "^9.1.13",
"vue": "^3.5.39",

diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7f5911ae..c2ff9c10 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -74,15 +74,9 @@ importers:
'@tanstack/vue-virtual':
specifier: ^3.13.30
version: 3.13.30(vue@3.5.39(typescript@6.0.3))
- click-outside-vue3:
- specifier: ^4.0.1
- version: 4.0.1
compressorjs:
specifier: ^1.3.0
version: 1.3.0
- dayjs:
- specifier: ^1.11.21
- version: 1.11.21
dompurify:
specifier: '>=3.4.11'
version: 3.4.11
@@ -104,9 +98,6 @@ importers:
marked:
specifier: ^18.0.5
version: 18.0.5
- mitt:
- specifier: ^3.0.1
- version: 3.0.1
ol:
specifier: ^10.9.0
version: 10.9.0(patch_hash=c7d2f18804523893b6652fabb2e43ca5120d818dbfc92b710a3f5ab3745d4ef4)
@@ -116,9 +107,6 @@ importers:
qrcode:
specifier: ^1.5.4
version: 1.5.4
- uuid:
- specifier: ^14.0.1
- version: 14.0.1
vis-data:
specifier: ^7.1.10
version: 7.1.10(uuid@14.0.1)(vis-util@5.0.7(@egjs/hammerjs@2.0.17)(component-emitter@2.0.0))
@@ -165,6 +153,9 @@ importers:
cross-env:
specifier: ^10.1.0
version: 10.1.0
+ dayjs:
+ specifier: ^1.11.21
+ version: 1.11.21
electron:
specifier: 42.4.0
version: 42.4.0
@@ -1497,10 +1488,6 @@ packages:
resolution: {integrity: sha512-c7YHpUyO1SaKaO7kYtxd5NZ8FjAmSK3LpKkuzdwn+2CwpFxBpdoQLm+OAnnCfoEl7onKhN9PKQi1lsHuAIUqGQ==}
engines: {node: '>=6'}
- click-outside-vue3@4.0.1:
- resolution: {integrity: sha512-sbplNecrup5oGqA3o4bo8XmvHRT6q9fvw21Z67aDbTqB9M6LF7CuYLTlLvNtOgKU6W3zst5H5zJuEh4auqA34g==}
- engines: {node: '>=6'}
-
cliui@4.0.0:
resolution: {integrity: sha512-nY3W5Gu2racvdDk//ELReY+dHjb9PlIcVDFXP72nVIhq2Gy3LuVXYwJoPVudwQnv1shtohpgkdCKT2YaKY0CKw==}
@@ -2797,9 +2784,6 @@ packages:
resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==}
engines: {node: '>= 18'}
- mitt@3.0.1:
- resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
-
mkdirp@0.5.1:
resolution: {integrity: sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA==}
deprecated: Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)
@@ -4286,7 +4270,7 @@ snapshots:
'@isaacs/cliui@8.0.2':
dependencies:
string-width: 5.1.2
- string-width-cjs: string-width@4.2.3
+ string-width-cjs: string-width@4.2.0
strip-ansi: 6.0.1
strip-ansi-cjs: strip-ansi@6.0.1
wrap-ansi: 8.1.0
@@ -5228,8 +5212,6 @@ snapshots:
optionalDependencies:
colors: 1.1.2
- click-outside-vue3@4.0.1: {}
-
cliui@4.0.0:
dependencies:
string-width: 2.1.1
@@ -6635,8 +6617,6 @@ snapshots:
dependencies:
minipass: 7.1.2
- mitt@3.0.1: {}
-
mkdirp@0.5.1:
dependencies:
minimist: 1.2.8

diff --git a/tests/backend/test_security_exploratory_oracle.py b/tests/backend/test_security_exploratory_oracle.py
index 4a30b190..73b0ab71 100644
--- a/tests/backend/test_security_exploratory_oracle.py
+++ b/tests/backend/test_security_exploratory_oracle.py
@@ -47,7 +47,9 @@ class _Sink:
st.none(),
st.integers(min_value=-10_000, max_value=10_000),
st.text(max_size=40),
- st.sampled_from(["", "15", "0", "-1", "999999", "1.5", "nan", "0x10", "timeout"]),
+ st.sampled_from(
+ ["", "15", "0", "-1", "999999", "1.5", "nan", "0x10", "timeout"]
+ ),
),
)
def test_oracle_call_timeout_always_in_bounds(raw):
@@ -139,7 +141,9 @@ def _try_b64(raw):
st.booleans(),
st.lists(st.text(max_size=8), max_size=3),
st.text(max_size=40),
- st.sampled_from(["png", "PNG", "image/jpeg", "svg", "svg+xml", "webm", "", " "]),
+ st.sampled_from(
+ ["png", "PNG", "image/jpeg", "svg", "svg+xml", "webm", "", " "]
+ ),
),
)
def test_oracle_image_type_always_allowlisted(image_type):

diff --git a/tests/electron/desktopPrivacySettings.test.js b/tests/electron/desktopPrivacySettings.test.js
index 95aac31e..9e4a779e 100644
--- a/tests/electron/desktopPrivacySettings.test.js
+++ b/tests/electron/desktopPrivacySettings.test.js
@@ -39,9 +39,7 @@ describe("electron/desktopPrivacySettings", () => {
expect(normalizeDesktopPrivacySettings({ screenSecurityEnabled: "yes" })).toEqual(
defaultDesktopPrivacySettings()
);
- expect(normalizeDesktopPrivacySettings({ screenSecurityEnabled: 1 })).toEqual(
- defaultDesktopPrivacySettings()
- );
+ expect(normalizeDesktopPrivacySettings({ screenSecurityEnabled: 1 })).toEqual(defaultDesktopPrivacySettings());
expect(normalizeDesktopPrivacySettings({ screenSecurityEnabled: true })).toEqual({
screenSecurityEnabled: true,
});

diff --git a/tests/frontend/Utils.test.js b/tests/frontend/Utils.test.js
index d73d6f13..f9e4bb73 100644
--- a/tests/frontend/Utils.test.js
+++ b/tests/frontend/Utils.test.js
@@ -1,6 +1,5 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import Utils from "@/js/Utils";
-import dayjs from "dayjs";
describe("Utils.js", () => {
describe("formatDestinationHash", () => {

diff --git a/tests/frontend/libs.core.test.js b/tests/frontend/libs.core.test.js
new file mode 100644
index 00000000..c35c791d
--- /dev/null
+++ b/tests/frontend/libs.core.test.js
@@ -0,0 +1,646 @@
+// SPDX-License-Identifier: 0BSD
+
+import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
+import { createApp, defineComponent, nextTick } from "vue";
+import dayjs from "dayjs";
+
+import createEmitterDefault, { createEmitter } from "@/libs/emitter.js";
+import { randomUuidV4, uuidv4, isUuidV4, fillRandomBytes, resolveCrypto, UUID_V4_RE } from "@/libs/uuid.js";
+import {
+ formatDate,
+ fromNow,
+ relativeLabel,
+ meshDate,
+ toDate,
+ isSupportedFormatPattern,
+ FROM_NOW_GOLDEN,
+ SUPPORTED_FORMAT_TOKENS,
+} from "@/libs/datetime.js";
+import clickOutsidePlugin, {
+ processDirectiveArguments,
+ bindingsEqual,
+ isClickOutsideElement,
+ execHandler,
+ onOutsideEvent,
+ onFauxIframeClick,
+ beforeMount,
+ updated,
+ unmounted,
+ HANDLERS_PROPERTY,
+ DEFAULT_EVENTS,
+} from "@/libs/clickOutside.js";
+
+function mulberry32(seed) {
+ let t = seed >>> 0;
+ return function next() {
+ t += 0x6d2b79f5;
+ let r = Math.imul(t ^ (t >>> 15), 1 | t);
+ r ^= r + Math.imul(r ^ (r >>> 7), 61 | r);
+ return ((r ^ (r >>> 14)) >>> 0) / 4294967296;
+ };
+}
+
+describe("libs/emitter", () => {
+ it("unit: on/emit/off round trip", () => {
+ const e = createEmitter();
+ const seen = [];
+ const handler = (v) => seen.push(v);
+ e.on("x", handler);
+ e.emit("x", 1);
+ e.off("x", handler);
+ e.emit("x", 2);
+ expect(seen).toEqual([1]);
+ });
+
+ it("unit: default export matches createEmitter", () => {
+ const e = createEmitterDefault();
+ const seen = [];
+ e.on("z", (v) => seen.push(v));
+ e.emit("z", 9);
+ expect(seen).toEqual([9]);
+ });
+
+ it("edge: off unknown handler and off-all are safe", () => {
+ const e = createEmitter();
+ const handler = () => {};
+ e.on("a", handler);
+ e.off("a", () => {});
+ e.off("missing");
+ e.off("a");
+ expect(e.all.get("a")).toEqual([]);
+ expect(() => e.emit("a", 1)).not.toThrow();
+ });
+
+ it("edge: non-function on is ignored", () => {
+ const e = createEmitter();
+ e.on("a", null);
+ e.on("a", 12);
+ expect(e.all.has("a")).toBe(false);
+ });
+
+ it("edge: emit during emit uses snapshot so removes do not skip peers", () => {
+ const e = createEmitter();
+ const order = [];
+ const b = () => order.push("b");
+ const a = () => {
+ order.push("a");
+ e.off("t", b);
+ };
+ e.on("t", a);
+ e.on("t", b);
+ e.emit("t");
+ expect(order).toEqual(["a", "b"]);
+ });
+
+ it("edge: symbol event types work", () => {
+ const e = createEmitter();
+ const key = Symbol("evt");
+ const seen = [];
+ e.on(key, (v) => seen.push(v));
+ e.emit(key, "ok");
+ expect(seen).toEqual(["ok"]);
+ });
+
+ it("edge: throwing handler does not prevent later handlers", () => {
+ const e = createEmitter();
+ const seen = [];
+ e.on("t", () => {
+ throw new Error("boom");
+ });
+ e.on("t", () => seen.push("later"));
+ expect(() => e.emit("t")).toThrow(/boom/);
+ expect(seen).toEqual([]);
+ const safe = createEmitter();
+ safe.on("t", () => {
+ try {
+ throw new Error("boom");
+ } catch {
+ /* swallow */
+ }
+ });
+ safe.on("t", () => seen.push("later"));
+ safe.emit("t");
+ expect(seen).toEqual(["later"]);
+ });
+
+ it("wildcard * receives type and payload", () => {
+ const e = createEmitter();
+ const seen = [];
+ e.on("*", (type, payload) => seen.push([type, payload]));
+ e.on("ping", () => {});
+ e.emit("ping", { ok: true });
+ expect(seen).toEqual([["ping", { ok: true }]]);
+ });
+
+ it("property: handler list length equals successful on calls", () => {
+ const rand = mulberry32(7);
+ for (let trial = 0; trial < 80; trial++) {
+ const e = createEmitter();
+ const n = 1 + Math.floor(rand() * 20);
+ for (let i = 0; i < n; i++) {
+ e.on("k", () => {});
+ }
+ expect(e.all.get("k").length).toBe(n);
+ }
+ });
+
+ it("fuzzing: random on/off/emit sequences never throw", () => {
+ const rand = mulberry32(99);
+ const e = createEmitter();
+ const handlers = [];
+ for (let i = 0; i < 1000; i++) {
+ const op = Math.floor(rand() * 4);
+ const type = rand() < 0.1 ? "*" : `e${Math.floor(rand() * 8)}`;
+ if (op === 0) {
+ const handler = () => {};
+ handlers.push(handler);
+ e.on(type, handler);
+ } else if (op === 1 && handlers.length) {
+ e.off(type, handlers[Math.floor(rand() * handlers.length)]);
+ } else if (op === 2) {
+ e.off(type);
+ } else {
+ e.emit(type, { i });
+ }
+ }
+ });
+});
+
+describe("libs/uuid", () => {
+ it("unit: uuidv4 matches RFC4122 v4 shape", () => {
+ const id = uuidv4();
+ expect(isUuidV4(id)).toBe(true);
+ expect(UUID_V4_RE.test(id)).toBe(true);
+ expect(id[14]).toBe("4");
+ expect("89ab").toContain(id[19].toLowerCase());
+ });
+
+ it("edge: isUuidV4 rejects junk", () => {
+ expect(isUuidV4(null)).toBe(false);
+ expect(isUuidV4("")).toBe(false);
+ expect(isUuidV4("not-a-uuid")).toBe(false);
+ expect(isUuidV4("00000000-0000-0000-0000-000000000000")).toBe(false);
+ expect(isUuidV4("00000000-0000-4000-0000-000000000000")).toBe(false);
+ expect(isUuidV4(12)).toBe(false);
+ });
+
+ it("edge: getRandomValues path when randomUUID missing", () => {
+ const fakeCrypto = {
+ getRandomValues(buf) {
+ for (let i = 0; i < buf.length; i++) buf[i] = (i * 17) & 0xff;
+ return buf;
+ },
+ };
+ const id = randomUuidV4({ crypto: fakeCrypto });
+ expect(isUuidV4(id)).toBe(true);
+ const bytes = fillRandomBytes(new Uint8Array(4), { crypto: fakeCrypto });
+ expect(Array.from(bytes)).toEqual([0, 17, 34, 51]);
+ });
+
+ it("edge: Math.random fallback when crypto is null", () => {
+ const spy = vi.spyOn(Math, "random").mockReturnValue(0.5);
+ try {
+ const id = randomUuidV4({ crypto: null });
+ expect(isUuidV4(id)).toBe(true);
+ const bytes = fillRandomBytes(new Uint8Array(3), { crypto: null });
+ expect(bytes.every((b) => b === 128)).toBe(true);
+ } finally {
+ spy.mockRestore();
+ }
+ });
+
+ it("edge: fillRandomBytes rejects non-Uint8Array", () => {
+ expect(() => fillRandomBytes(/** @type {any} */ ([]))).toThrow(/Uint8Array/);
+ });
+
+ it("edge: resolveCrypto respects explicit null override", () => {
+ expect(resolveCrypto({ crypto: null })).toBeNull();
+ expect(resolveCrypto({})).toBeTruthy();
+ const original = globalThis.crypto;
+ vi.stubGlobal("crypto", undefined);
+ try {
+ expect(resolveCrypto({})).toBeUndefined();
+ expect(isUuidV4(randomUuidV4({}))).toBe(true);
+ } finally {
+ vi.stubGlobal("crypto", original);
+ }
+ });
+
+ it("property: generated ids are unique over large samples", () => {
+ const set = new Set();
+ for (let i = 0; i < 5000; i++) {
+ set.add(uuidv4());
+ }
+ expect(set.size).toBe(5000);
+ });
+
+ it("property: no-crypto ids still unique and valid", () => {
+ const set = new Set();
+ for (let i = 0; i < 1000; i++) {
+ const id = uuidv4({ crypto: null });
+ expect(isUuidV4(id)).toBe(true);
+ set.add(id);
+ }
+ expect(set.size).toBe(1000);
+ });
+
+ it("fuzzing: fillRandomBytes never throws for sizes 0..128", () => {
+ for (let n = 0; n <= 128; n++) {
+ expect(() => fillRandomBytes(new Uint8Array(n), { crypto: null })).not.toThrow();
+ expect(() => fillRandomBytes(new Uint8Array(n))).not.toThrow();
+ }
+ });
+});
+
+describe("libs/datetime", () => {
+ it("unit: formatDate tokens used by MeshChatX", () => {
+ const d = new Date(2025, 0, 2, 15, 4, 0);
+ expect(formatDate(d, "MMM D, h:mm A")).toBe("Jan 2, 3:04 PM");
+ expect(formatDate(d, "YYYY-MM-DD hh:mm A")).toBe("2025-01-02 03:04 PM");
+ expect(formatDate(d, "MMM D, HH:mm")).toBe("Jan 2, 15:04");
+ expect(formatDate(d, "M/D H:mm a")).toBe("1/2 15:04 pm");
+ expect(formatDate(new Date(2025, 0, 2, 0, 0, 0), "h A")).toBe("12 AM");
+ expect(formatDate(new Date(2025, 0, 2, 12, 0, 0), "h A")).toBe("12 PM");
+ expect(formatDate(d, "DD MM YYYY")).toBe("02 01 2025");
+ });
+
+ it("unit: isSupportedFormatPattern", () => {
+ expect(isSupportedFormatPattern("MMM D, h:mm A")).toBe(true);
+ expect(isSupportedFormatPattern("plain text")).toBe(true);
+ expect(isSupportedFormatPattern("YYYY-MM-DD ss")).toBe(true);
+ expect(isSupportedFormatPattern("")).toBe(false);
+ expect(isSupportedFormatPattern(null)).toBe(false);
+ expect(SUPPORTED_FORMAT_TOKENS).toContain("YYYY");
+ expect(isSupportedFormatPattern("!!!")).toBe(true);
+ });
+
+ it("edge: toDate and formatDate on invalid inputs", () => {
+ expect(toDate(null)).toBeNull();
+ expect(toDate("")).toBeNull();
+ expect(toDate(Number.NaN)).toBeNull();
+ expect(toDate(Infinity)).toBeNull();
+ expect(toDate("not-a-date")).toBeNull();
+ expect(toDate({})).toBeNull();
+ expect(toDate(new Date(Number.NaN))).toBeNull();
+ expect(formatDate(null, "YYYY")).toBe("");
+ expect(formatDate(Date.now(), "")).toBe("");
+ expect(formatDate(Date.now(), null)).toBe("");
+ expect(fromNow(null)).toBe("");
+ expect(fromNow(Date.now(), null)).toBe("");
+ });
+
+ it("golden: FROM_NOW_GOLDEN table is exact", () => {
+ const now = Date.parse("2025-06-01T12:00:00Z");
+ for (const [deltaSec, expectedPast, expectedFuture] of FROM_NOW_GOLDEN) {
+ expect(relativeLabel(deltaSec, false)).toBe(expectedPast);
+ expect(relativeLabel(deltaSec, true)).toBe(deltaSec === 0 ? expectedPast : expectedFuture);
+ expect(fromNow(now - deltaSec * 1000, now)).toBe(expectedPast);
+ if (deltaSec === 0) {
+ expect(fromNow(now, now)).toBe(expectedPast);
+ } else {
+ expect(fromNow(now + deltaSec * 1000, now)).toBe(expectedFuture);
+ }
+ }
+ });
+
+ it("oracle: formatDate matches dayjs for MeshChatX patterns", () => {
+ const patterns = ["MMM D, h:mm A", "YYYY-MM-DD hh:mm A", "MMM D, HH:mm", "M", "D", "H", "a"];
+ const stamps = [
+ new Date(2020, 0, 1, 0, 0, 0),
+ new Date(2024, 5, 15, 12, 30, 0),
+ new Date(2025, 11, 31, 23, 59, 0),
+ new Date(2026, 6, 17, 21, 34, 0),
+ new Date(),
+ ];
+ for (const ts of stamps) {
+ for (const pattern of patterns) {
+ expect(formatDate(ts, pattern)).toBe(dayjs(ts).format(pattern));
+ }
+ }
+ });
+
+ it("property: fromNow polarity matches sign of (input - now)", () => {
+ const rand = mulberry32(42);
+ const now = Date.now();
+ for (let i = 0; i < 400; i++) {
+ const deltaSec = Math.floor(rand() * 400 * 86400) - 200 * 86400;
+ if (deltaSec === 0) continue;
+ const out = fromNow(now + deltaSec * 1000, now);
+ expect(out.length).toBeGreaterThan(0);
+ if (deltaSec > 0) {
+ expect(out.startsWith("in ")).toBe(true);
+ } else {
+ expect(out.endsWith(" ago")).toBe(true);
+ }
+ }
+ });
+
+ it("fuzzing: meshDate never throws on random inputs", () => {
+ const rand = mulberry32(3);
+ const junk = [null, undefined, "", "x", {}, [], Number.NaN, Infinity, -Infinity, true, false];
+ for (let i = 0; i < 500; i++) {
+ const input = rand() < 0.3 ? junk[Math.floor(rand() * junk.length)] : Date.now() + (rand() - 0.5) * 1e12;
+ const d = meshDate(input);
+ expect(() => d.format("MMM D, h:mm A")).not.toThrow();
+ expect(() => d.fromNow()).not.toThrow();
+ expect(() => d.toDate()).not.toThrow();
+ }
+ });
+});
+
+describe("libs/clickOutside", () => {
+ beforeEach(() => {
+ document.body.innerHTML = "";
+ vi.useFakeTimers();
+ });
+
+ afterEach(() => {
+ vi.useRealTimers();
+ document.body.innerHTML = "";
+ });
+
+ it("unit: processDirectiveArguments accepts function and object forms", () => {
+ const fn = () => {};
+ expect(processDirectiveArguments(fn).handler).toBe(fn);
+ const obj = processDirectiveArguments({
+ handler: fn,
+ capture: true,
+ isActive: false,
+ detectIframe: false,
+ events: ["mousedown"],
+ });
+ expect(obj.capture).toBe(true);
+ expect(obj.isActive).toBe(false);
+ expect(obj.detectIframe).toBe(false);
+ expect(obj.events).toEqual(["mousedown"]);
+ expect(DEFAULT_EVENTS.length).toBeGreaterThan(0);
+ });
+
+ it("unit: bindingsEqual preserves function identity", () => {
+ const a = () => {};
+ const b = () => {};
+ expect(bindingsEqual(a, a)).toBe(true);
+ expect(bindingsEqual(a, b)).toBe(false);
+ expect(bindingsEqual({ handler: a, capture: true }, { handler: a, capture: true })).toBe(true);
+ expect(bindingsEqual({ handler: a, capture: true }, { handler: a, capture: false })).toBe(false);
+ expect(bindingsEqual({ handler: a }, { handler: b })).toBe(false);
+ expect(bindingsEqual(null, { handler: a })).toBe(false);
+ expect(bindingsEqual(1, 2)).toBe(false);
+ expect(bindingsEqual({ handler: a }, null)).toBe(false);
+ });
+
+ it("edge: invalid bindings throw", () => {
+ expect(() => processDirectiveArguments(null)).toThrow(/function or an object/);
+ expect(() => processDirectiveArguments("x")).toThrow(/function or an object/);
+ expect(() => processDirectiveArguments({})).toThrow(/handler must be a function/);
+ });
+
+ it("unit: isClickOutsideElement uses contains, composedPath, and path fallback", () => {
+ const root = document.createElement("div");
+ const child = document.createElement("span");
+ root.appendChild(child);
+ document.body.appendChild(root);
+ expect(
+ isClickOutsideElement({
+ el: root,
+ event: { target: child, composedPath: () => [child, root, document.body] },
+ })
+ ).toBe(false);
+ expect(
+ isClickOutsideElement({
+ el: root,
+ event: { target: document.body, path: [document.body] },
+ })
+ ).toBe(true);
+ expect(
+ isClickOutsideElement({
+ el: root,
+ event: { target: child },
+ })
+ ).toBe(false);
+ expect(
+ isClickOutsideElement({
+ el: root,
+ event: { target: null },
+ })
+ ).toBe(true);
+ });
+
+ it("unit: middleware can block handler", () => {
+ const handler = vi.fn();
+ execHandler({ event: {}, handler, middleware: () => false });
+ expect(handler).not.toHaveBeenCalled();
+ execHandler({ event: { ok: 1 }, handler, middleware: () => true });
+ expect(handler).toHaveBeenCalledOnce();
+ });
+
+ it("lifecycle: beforeMount/updated/unmounted attach and detach listeners", () => {
+ const el = document.createElement("div");
+ document.body.appendChild(el);
+ const outside = document.createElement("button");
+ document.body.appendChild(outside);
+ const handler1 = vi.fn();
+ const handler2 = vi.fn();
+
+ beforeMount(el, { value: { handler: handler1, events: ["click"], detectIframe: false } });
+ vi.runAllTimers();
+ expect(el[HANDLERS_PROPERTY].length).toBe(1);
+
+ const clickEvent = new MouseEvent("click", { bubbles: true });
+ Object.defineProperty(clickEvent, "composedPath", {
+ value: () => [outside, document.body, document.documentElement],
+ });
+ document.documentElement.dispatchEvent(clickEvent);
+ expect(handler1).toHaveBeenCalled();
+
+ updated(el, {
+ value: { handler: handler1, events: ["click"], detectIframe: false },
+ oldValue: { handler: handler1, events: ["click"], detectIframe: false },
+ });
+ expect(handler1).toHaveBeenCalledTimes(1);
+
+ updated(el, {
+ value: { handler: handler2, events: ["click"], detectIframe: false },
+ oldValue: { handler: handler1, events: ["click"], detectIframe: false },
+ });
+ vi.runAllTimers();
+ document.documentElement.dispatchEvent(clickEvent);
+ expect(handler2).toHaveBeenCalled();
+
+ unmounted(el);
+ expect(el[HANDLERS_PROPERTY]).toBeUndefined();
+ document.documentElement.dispatchEvent(clickEvent);
+ expect(handler2).toHaveBeenCalledTimes(1);
+ });
+
+ it("lifecycle: isActive false skips listeners", () => {
+ const el = document.createElement("div");
+ document.body.appendChild(el);
+ beforeMount(el, { value: { handler: () => {}, isActive: false } });
+ expect(el[HANDLERS_PROPERTY]).toBeUndefined();
+ });
+
+ it("lifecycle: unmount before delayed attach is safe", () => {
+ const el = document.createElement("div");
+ document.body.appendChild(el);
+ const addSpy = vi.spyOn(document.documentElement, "addEventListener");
+ beforeMount(el, { value: { handler: () => {}, events: ["click"], detectIframe: false } });
+ expect(el[HANDLERS_PROPERTY]).toBeTruthy();
+ unmounted(el);
+ expect(el[HANDLERS_PROPERTY]).toBeUndefined();
+ vi.runAllTimers();
+ expect(addSpy).not.toHaveBeenCalled();
+ addSpy.mockRestore();
+ });
+
+ it("iframe: blur listener invokes onFauxIframeClick path", () => {
+ const el = document.createElement("div");
+ document.body.appendChild(el);
+ const iframe = document.createElement("iframe");
+ document.body.appendChild(iframe);
+ const handler = vi.fn();
+ beforeMount(el, { value: { handler, events: ["click"], detectIframe: true } });
+ vi.runAllTimers();
+ const activeSpy = vi.spyOn(document, "activeElement", "get").mockReturnValue(iframe);
+ try {
+ window.dispatchEvent(new Event("blur"));
+ vi.runAllTimers();
+ expect(handler).toHaveBeenCalled();
+ } finally {
+ activeSpy.mockRestore();
+ unmounted(el);
+ }
+ });
+
+ it("iframe: onFauxIframeClick fires when activeElement is outside iframe", () => {
+ const el = document.createElement("div");
+ document.body.appendChild(el);
+ const iframe = document.createElement("iframe");
+ document.body.appendChild(iframe);
+ const handler = vi.fn();
+ const activeSpy = vi.spyOn(document, "activeElement", "get").mockReturnValue(iframe);
+ try {
+ onFauxIframeClick({
+ el,
+ event: new Event("blur"),
+ handler,
+ middleware: (x) => x,
+ });
+ vi.runAllTimers();
+ expect(handler).toHaveBeenCalledOnce();
+ } finally {
+ activeSpy.mockRestore();
+ }
+ });
+
+ it("iframe: onFauxIframeClick ignores iframe contained by el", () => {
+ const el = document.createElement("div");
+ const iframe = document.createElement("iframe");
+ el.appendChild(iframe);
+ document.body.appendChild(el);
+ const handler = vi.fn();
+ const activeSpy = vi.spyOn(document, "activeElement", "get").mockReturnValue(iframe);
+ try {
+ onFauxIframeClick({
+ el,
+ event: new Event("blur"),
+ handler,
+ middleware: (x) => x,
+ });
+ vi.runAllTimers();
+ expect(handler).not.toHaveBeenCalled();
+ } finally {
+ activeSpy.mockRestore();
+ }
+ });
+
+ it("iframe: detectIframe registers blur listener via beforeMount", () => {
+ const el = document.createElement("div");
+ document.body.appendChild(el);
+ const handler = vi.fn();
+ beforeMount(el, { value: { handler, detectIframe: true } });
+ vi.runAllTimers();
+ expect(el[HANDLERS_PROPERTY].some((entry) => entry.event === "blur")).toBe(true);
+ unmounted(el);
+ });
+
+ it("vue: plugin install and real directive closes on outside click", async () => {
+ vi.useRealTimers();
+ const handler = vi.fn();
+ const Root = defineComponent({
+ template: `
+ <div>
+ <div id="inside" v-click-outside="outsideOpts">inside</div>
+ <button id="outside" type="button">out</button>
+ </div>
+ `,
+ data() {
+ return {
+ outsideOpts: {
+ handler,
+ events: ["click"],
+ detectIframe: false,
+ },
+ };
+ },
+ });
+ const app = createApp(Root);
+ app.use(clickOutsidePlugin);
+ const root = document.createElement("div");
+ document.body.appendChild(root);
+ app.mount(root);
+ await nextTick();
+ await new Promise((r) => setTimeout(r, 20));
+
+ const outside = document.getElementById("outside");
+ const evt = new MouseEvent("click", { bubbles: true, cancelable: true });
+ outside.dispatchEvent(evt);
+ await nextTick();
+ expect(handler).toHaveBeenCalled();
+ app.unmount();
+ });
+
+ it("unit: onOutsideEvent ignores inside clicks", () => {
+ const el = document.createElement("div");
+ const child = document.createElement("span");
+ el.appendChild(child);
+ const handler = vi.fn();
+ onOutsideEvent({
+ el,
+ event: { target: child, composedPath: () => [child, el] },
+ handler,
+ middleware: (x) => x,
+ });
+ expect(handler).not.toHaveBeenCalled();
+ });
+
+ it("fuzzing: processDirectiveArguments and isClickOutsideElement stay safe", () => {
+ const rand = mulberry32(11);
+ const handler = () => {};
+ for (let i = 0; i < 300; i++) {
+ const value =
+ rand() < 0.5
+ ? handler
+ : {
+ handler,
+ capture: rand() < 0.5,
+ isActive: rand() < 0.8,
+ detectIframe: rand() < 0.8,
+ events: rand() < 0.3 ? ["mousedown"] : undefined,
+ middleware: rand() < 0.5 ? () => true : undefined,
+ };
+ expect(() => processDirectiveArguments(value)).not.toThrow();
+ const el = document.createElement("div");
+ const target = document.createElement("span");
+ expect(() =>
+ isClickOutsideElement({
+ el,
+ event: {
+ target,
+ composedPath: () => (rand() < 0.5 ? [target] : [target, el]),
+ },
+ })
+ ).not.toThrow();
+ }
+ });
+});

diff --git a/tests/frontend/libs.index.test.js b/tests/frontend/libs.index.test.js
new file mode 100644
index 00000000..5ecca2e4
--- /dev/null
+++ b/tests/frontend/libs.index.test.js
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: 0BSD
+
+import { describe, it, expect } from "vitest";
+import * as libs from "@/libs/index.js";
+
+describe("libs/index barrel", () => {
+ it("exports the public MeshChatX lib surface", () => {
+ expect(typeof libs.createEmitter).toBe("function");
+ expect(typeof libs.emitter).toBe("function");
+ expect(typeof libs.uuidv4).toBe("function");
+ expect(typeof libs.randomUuidV4).toBe("function");
+ expect(typeof libs.formatDate).toBe("function");
+ expect(typeof libs.fromNow).toBe("function");
+ expect(typeof libs.relativeLabel).toBe("function");
+ expect(typeof libs.meshDate).toBe("function");
+ expect(typeof libs.clickOutside.install).toBe("function");
+ expect(typeof libs.processDirectiveArguments).toBe("function");
+ expect(typeof libs.bindingsEqual).toBe("function");
+ expect(Array.isArray(libs.FROM_NOW_GOLDEN)).toBe(true);
+ expect(Array.isArray(libs.SUPPORTED_FORMAT_TOKENS)).toBe(true);
+ expect(libs.FROM_NOW_GOLDEN.length).toBeGreaterThan(10);
+ });
+});

diff --git a/tests/frontend/libs.oracle.test.js b/tests/frontend/libs.oracle.test.js
new file mode 100644
index 00000000..c875d66f
--- /dev/null
+++ b/tests/frontend/libs.oracle.test.js
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: 0BSD
+
+/**
+ * Differential oracles against dayjs (devDependency) plus frozen golden
+ * and cross-lib property invariants for MeshChatX frontend libs.
+ */
+
+import { describe, it, expect } from "vitest";
+import dayjs from "dayjs";
+import { createEmitter } from "@/libs/emitter.js";
+import { formatDate, fromNow, relativeLabel, FROM_NOW_GOLDEN, isSupportedFormatPattern } from "@/libs/datetime.js";
+import { uuidv4, isUuidV4, randomUuidV4 } from "@/libs/uuid.js";
+import { processDirectiveArguments, bindingsEqual } from "@/libs/clickOutside.js";
+
+function mulberry32(seed) {
+ let t = seed >>> 0;
+ return function next() {
+ t += 0x6d2b79f5;
+ let r = Math.imul(t ^ (t >>> 15), 1 | t);
+ r ^= r + Math.imul(r ^ (r >>> 7), 61 | r);
+ return ((r ^ (r >>> 14)) >>> 0) / 4294967296;
+ };
+}
+
+describe("libs oracles", () => {
+ it("oracle: formatDate equals dayjs for dense local calendar sampling", () => {
+ const patterns = ["MMM D, h:mm A", "YYYY-MM-DD hh:mm A", "MMM D, HH:mm", "YYYY-MM-DD", "h A", "hh:mm a"];
+ const rand = mulberry32(2026);
+ for (let i = 0; i < 600; i++) {
+ const year = 2018 + Math.floor(rand() * 12);
+ const month = Math.floor(rand() * 12);
+ const day = 1 + Math.floor(rand() * 28);
+ const hour = Math.floor(rand() * 24);
+ const minute = Math.floor(rand() * 60);
+ const d = new Date(year, month, day, hour, minute, 0, 0);
+ for (const pattern of patterns) {
+ expect(isSupportedFormatPattern(pattern)).toBe(true);
+ expect(formatDate(d, pattern)).toBe(dayjs(d).format(pattern));
+ }
+ }
+ });
+
+ it("oracle: FROM_NOW_GOLDEN matches relativeLabel and fromNow", () => {
+ const now = Date.parse("2024-03-15T08:00:00Z");
+ for (const [deltaSec, expectedPast, expectedFuture] of FROM_NOW_GOLDEN) {
+ expect(relativeLabel(deltaSec, false)).toBe(expectedPast);
+ expect(fromNow(now - deltaSec * 1000, now)).toBe(expectedPast);
+ if (deltaSec === 0) {
+ expect(relativeLabel(0, true)).toBe(expectedPast);
+ expect(fromNow(now, now)).toBe(expectedPast);
+ } else {
+ expect(relativeLabel(deltaSec, true)).toBe(expectedFuture);
+ expect(fromNow(now + deltaSec * 1000, now)).toBe(expectedFuture);
+ }
+ }
+ });
+
+ it("oracle: emitter semantics match mitt-style fanout and off-all", () => {
+ const e = createEmitter();
+ const a = [];
+ const b = [];
+ const ha = (v) => a.push(v);
+ const hb = (v) => b.push(v);
+ e.on("msg", ha);
+ e.on("msg", hb);
+ e.emit("msg", 1);
+ e.off("msg");
+ e.emit("msg", 2);
+ expect(a).toEqual([1]);
+ expect(b).toEqual([1]);
+ expect(e.all.get("msg")).toEqual([]);
+ });
+
+ it("oracle: uuidv4 always validates as v4 and never collides in batch", () => {
+ const batch = Array.from({ length: 8000 }, () => uuidv4());
+ for (const id of batch) {
+ expect(isUuidV4(id)).toBe(true);
+ }
+ expect(new Set(batch).size).toBe(batch.length);
+ });
+
+ it("oracle: no-crypto uuid path still RFC4122 v4", () => {
+ const batch = Array.from({ length: 2000 }, () => randomUuidV4({ crypto: null }));
+ for (const id of batch) {
+ expect(isUuidV4(id)).toBe(true);
+ expect(id[14]).toBe("4");
+ }
+ expect(new Set(batch).size).toBe(batch.length);
+ });
+
+ it("oracle: click-outside function form is equivalent to object form defaults", () => {
+ const handler = () => {};
+ const a = processDirectiveArguments(handler);
+ const b = processDirectiveArguments({ handler });
+ expect(a.handler).toBe(b.handler);
+ expect(a.isActive).toBe(true);
+ expect(b.isActive).toBe(true);
+ expect(a.detectIframe).toBe(true);
+ expect(a.capture).toBe(false);
+ expect(typeof a.middleware).toBe("function");
+ expect(a.events).toEqual(b.events);
+ expect(bindingsEqual(handler, handler)).toBe(true);
+ expect(bindingsEqual({ handler, capture: true }, { handler, capture: true })).toBe(true);
+ });
+
+ it("property: fromNow polarity matches sign of (input - now)", () => {
+ const rand = mulberry32(77);
+ const now = Date.parse("2026-01-01T00:00:00Z");
+ for (let i = 0; i < 500; i++) {
+ const deltaMs = Math.floor((rand() - 0.5) * 2 * 365 * 86400 * 1000);
+ if (deltaMs === 0) continue;
+ const out = fromNow(now + deltaMs, now);
+ if (deltaMs > 0) {
+ expect(out.startsWith("in ")).toBe(true);
+ } else {
+ expect(out.endsWith(" ago")).toBe(true);
+ }
+ }
+ });
+
+ it("property: formatDate is idempotent for the same Date instance", () => {
+ const d = new Date(2024, 3, 5, 9, 8);
+ const pattern = "YYYY-MM-DD hh:mm A";
+ expect(formatDate(d, pattern)).toBe(formatDate(d, pattern));
+ expect(formatDate(d.getTime(), pattern)).toBe(formatDate(d, pattern));
+ });
+
+ it("property: relativeLabel is monotonic in bucket wording for increasing deltas", () => {
+ const samples = FROM_NOW_GOLDEN.map(([delta]) => delta).sort((a, b) => a - b);
+ for (let i = 1; i < samples.length; i++) {
+ const prev = relativeLabel(samples[i - 1], false);
+ const cur = relativeLabel(samples[i], false);
+ expect(typeof prev).toBe("string");
+ expect(typeof cur).toBe("string");
+ expect(prev.length).toBeGreaterThan(0);
+ expect(cur.length).toBeGreaterThan(0);
+ }
+ });
+});

diff --git a/vite.config.js b/vite.config.js
index 72417030..fec2405c 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -11,11 +11,8 @@ const vendorChunkGroups = [
{ test: /[/\\]node_modules[/\\](vis-network|vis-data)/, name: "vendor-vis", priority: 95 },
{ test: /[/\\]node_modules[/\\]vue-router/, name: "vendor-vue-router", priority: 90 },
{ test: /[/\\]node_modules[/\\](protobufjs|@protobufjs)/, name: "vendor-protobuf", priority: 85 },
- { test: /[/\\]node_modules[/\\]dayjs/, name: "vendor-dayjs", priority: 80 },
{ test: /[/\\]node_modules[/\\]@mdi(?:\/|\\)js/, name: "vendor-mdi", priority: 75 },
{ test: /[/\\]node_modules[/\\]compressorjs/, name: "vendor-compressor", priority: 70 },
- { test: /[/\\]node_modules[/\\]click-outside-vue3/, name: "vendor-click-outside", priority: 65 },
- { test: /[/\\]node_modules[/\\]mitt/, name: "vendor-mitt", priority: 60 },
{ test: /[/\\]node_modules[/\\]micron-parser/, name: "vendor-micron", priority: 55 },
{ test: /MicronParser\.js/, name: "vendor-micron", priority: 55 },
{ test: /[/\\]node_modules[/\\]electron-prompt/, name: "vendor-electron-prompt", priority: 50 },
@@ -271,7 +268,7 @@ export default defineConfig({
},
optimizeDeps: {
- include: ["dayjs", "vue", "emoji-picker-element"],
+ include: ["vue", "emoji-picker-element"],
},
resolve: {


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────